package com.honvay.cola.auth.oauth2.configuration;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
import org.springframework.security.oauth2.provider.token.TokenStore;
// import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import com.honvay.cola.auth.oauth2.provider.client.CustomJdbcClientDetailsService;
import com.honvay.cola.auth.oauth2.provider.endpoint.DefaultColaRedirectResolver;

/**
 * @author LIQIU
 */
@AutoConfigureAfter(WebSecurityConfiguration.class)
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

	@Autowired
	private DataSource dataSource;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private RedisConnectionFactory redisConnectionFactory;
	
	@Autowired
	private UserDetailsService userDetailsService;

	AuthorizationServerEndpointsConfigurer endpoints;
	// @Autowired(required = false)
//	private List<TokenGranter> tokenGranters;
	
//	public AuthorizationServerConfiguration (@Autowired(required = false) List<TokenGranter> tokenGranters){
//		this.tokenGranters = tokenGranters;
//	}

	@Bean
	public RedisTemplate<String, OAuth2Authentication> oauthRedisTemplate() {
		RedisTemplate<String, OAuth2Authentication> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
		return template;
	}

	@Bean
	public JdbcClientDetailsService jdbcClientDetailsService() {
		return new CustomJdbcClientDetailsService(dataSource);
	}

	@Bean
	public TokenStore tokenStore() {
		return new RedisTokenStore(redisConnectionFactory);
	}

	@Bean
	public ApprovalStore approvalStore() {
		TokenApprovalStore approvalStore = new TokenApprovalStore();
		approvalStore.setTokenStore(tokenStore());
		return approvalStore;
	}

	@Bean
	public AuthorizationCodeServices authorizationCodeServices() {
		return new JdbcAuthorizationCodeServices(dataSource);
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(jdbcClientDetailsService());
	}
	

    /**  
     * allowFormAuthenticationForClients 允许client使用form的方式进行authentication的授权
     * allowFormAuthenticationForClients 如果不设置在其他客户端使用表单提交的时候会返回401状态
     * 
     *  
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer)
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.realm("spring-oauth-server").allowFormAuthenticationForClients();
    }
	
    // TOKEN暂时不使用jwt的token使用普通token即可
//	@Bean
//	public JwtAccessTokenConverter accessTokenConverter() {
//		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//		converter.setSigningKey("cola-auth");
//		return converter;
//	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
		endpoints.approvalStore(approvalStore())
				.authorizationCodeServices(authorizationCodeServices())
				.tokenStore(tokenStore())
//				.accessTokenConverter(accessTokenConverter())
				// spring boot Oauth2 refresh_token UserDetailsService is required
				.userDetailsService(userDetailsService)
				.authenticationManager(authenticationManager);
		
		endpoints.redirectResolver(redirectResolver());
		// this.configGranters(endpoints);
		this.endpoints = endpoints;
	}

	/**
	 * 允许自由跳转到其他路径
	 * @return
	 */
	@Bean
	public RedirectResolver redirectResolver() {
		return new DefaultColaRedirectResolver();
	}

	/**
	 * 配置Token授权方式
	 *
	 * @param endpoints
	 */
	@Configuration
	class TokenGranterConfiguration {
		
		private List<TokenGranter> tokenGranters;
		
		TokenGranterConfiguration(@Autowired(required = false) List<TokenGranter> tokenGranters){
			this.tokenGranters = tokenGranters;
		}

		@PostConstruct
		private void configGranters() {
			if (tokenGranters != null) {
				List<TokenGranter> tgs = new ArrayList<TokenGranter>();
				tgs.add(endpoints.getTokenGranter());
				CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(tgs);
				tokenGranters.forEach(compositeTokenGranter::addTokenGranter);
				endpoints.tokenGranter(compositeTokenGranter);
			}
		}

	}
}
